【SwiftUI】ポップアップで表示されるダイアログを作ってみた
ポップアップで表示されるようなダイアログを作ってみたくなったので作ってみることにしました。
環境
- Xcode 13.3
- iOS 15.5
作ったもの
こんな感じで表示されるダイアログを作成してみました。
中身のContentは好きなものを渡せるようにしました。
コード
PopUpDialogView
struct PopUpDialogView<Content: View>: View { @Binding var isPresented: Bool let content: Content let isEnabledToCloseByBackgroundTap: Bool private let buttonSize: CGFloat = 24 var body: some View { GeometryReader { proxy in let dialogWidth = proxy.size.width * 0.75 ZStack { BackgroundView(color: .gray.opacity(0.7)) .onTapGesture { if isEnabledToCloseByBackgroundTap { withAnimation { isPresented = false } } } content .frame(width: dialogWidth) .padding() .padding(.top, buttonSize) .background(.white) .cornerRadius(12) .overlay(alignment: .topTrailing) { CloseButton(fontSize: buttonSize, weight: .bold, color: .gray.opacity(0.7)) { withAnimation { isPresented = false } } .padding(4) } } } } }
init
extension PopUpDialogView { init(isPresented: Binding<Bool>, isEnabledToCloseByBackgroundTap: Bool = true, @ViewBuilder _ content: () -> Content) { _isPresented = isPresented self.isEnabledToCloseByBackgroundTap = isEnabledToCloseByBackgroundTap self.content = content() } }
- isPresented
- ポップアップダイアログが表示されているか
- isEnabledToCloseByBackgroundTap
- ダイアログの背景をタップしても画面を閉じることが出来るかどうか
- content
- ダイアログの中に埋め込むContent
ダイアログのwidth
今回はダイアログのwidth
は、GeometryReader
からwidth
を取得して、その0.75
倍にしています。特に理由はないので好きな値にしていただければと思います。
GeometryReader { proxy in let dialogWidth = proxy.size.width * 0.75
Background
ポップアップダイアログのグレーがかった背景です。
BackgroundView(color: .gray.opacity(0.7)) .onTapGesture { if isEnabledToCloseByBackgroundTap { withAnimation { isPresented = false } } }
struct BackgroundView: View { let color: Color var body: some View { Rectangle() .fill(color) .ignoresSafeArea() } }
onTapGesture
で、もし背景タップで画面を閉じるようにしている場合は、isPresented
をfalse
に切り替えて画面を閉じます。ポップアップっぽく見せる為にwithAnimation
内でフラグ切替処理を行っております。
content
content .frame(width: dialogWidth) .padding() .padding(.top, buttonSize) .background(.white) .cornerRadius(12)
width
は、proxy.size.width
の0.75
倍の大きさにし、padding
を四方向に足した後に、上部に閉じるボタンのサイズ分だけさらにpadding
を追加しています。また、背景色を白にして、角丸に変更しています。
CloseButton
.overlay(alignment: .topTrailing) { CloseButton(fontSize: buttonSize, weight: .bold, color: .gray.opacity(0.7)) { withAnimation { isPresented = false } } .padding(4) }
struct CloseButton: View { let fontSize: CGFloat let weight: Font.Weight let color: Color let action: () -> Void var body: some View { Button { action() } label: { Image(systemName: "xmark.circle") } .font(.system(size: fontSize, weight: weight, design: .default)) .foregroundColor(color) } }
ダイアログの閉じるボタンは、.overlay(alignment: .topTrailing)
を使用して右上に配置して、CloseButton
を押した時のアクションは、withAnimation
の中でダイアログ表示のフラグをfalse
に切り替えています。
使用例
あとは、PopUpDialogViewに埋め込みたいViewを埋め込んで、任意のタイミングでダイアログ表示のフラグを切り替え表示するだけです。
struct ContentView: View { @State private var shouldPresentPopUpDialog = false var body: some View { ZStack { Button { withAnimation { shouldPresentPopUpDialog = true } } label: { Text("Present Pop-up Dialog") } if shouldPresentPopUpDialog { PopUpDialogView(isPresented: $shouldPresentPopUpDialog) { Face() } } } } }
おわりに
コードはGitHubに載せております。
とりあえず、やってみたかったことは出来ました!withAnimation
には他にもアニメーションがあるので、色々試してみて遊んでみるのも楽しそうですね!他にもハーフモーダルなど試してみたいことはあるので、機会があればやってみようと思います。